GitHub Actions are event-driven automation tasks that live within a GitHub repository. An event like a pull request can trigger a set of tasks to be executed. An example is a pull request triggering a set of tasks to clone the Git repository and execute go test to run Go tests.

GitHub Actions is extremely flexible, enabling developers to author a wide variety of automation, even some that we might not normally associate with a traditional continuous integration/release pipeline. Actions are also composable, enabling groups of tasks to be packaged together as a published action and used in workflows together with other actions.

svg viewer

In this lesson, we will learn about the components of a GitHub Action: workflows, events, context and expressions, jobs, steps, and actions. After we have been introduced to these components, we'll build and trigger our first GitHub Action.

Exploring the components of a GitHub Action#

Understanding the components of a GitHub Action, their relationships, and how they interact is the key to understanding how to compose our own automation. Let's get started with exploring the components of an action.

Workflows#

A workflow is an automation file written in YAML that lives in a GitHub repository in the ./github/workflows/ folder. A workflow consists of one or more jobs and can be scheduled or triggered by an event. A workflow is the highest-level component of a GitHub Action.

Workflow syntax#

Workflows require a developer to specify the events that will trigger automation via the on key and the jobs that automation will execute when it is triggered by the jobs key. Often, a name is also specified by the name keyword. Otherwise, the workflow will take the short name of the file that contains the workflow YAML. For example, the workflow defined in ./github/workflows/foo.yaml will have the default name of foo.

The following is an example of a named workflow with the minimum set of keys defined. However, this is not a valid workflow, as we have not yet defined any events to trigger the workflow nor any jobs to be executed once triggered:

Basic depiction of a workflow

Next, let's discuss how to trigger workflows.

Events#

An event is a trigger that causes a workflow to start executing. Events come in a variety of flavors: webhook events, scheduled events, and manually dispatched events.

Webhook events can originate from an activity within the repository. Examples of triggering activities are pushing a commit, creating a pull request, or creating a new issue. Events raised from repository interactions are the most common triggers for workflows. Webhook events can also be created through external systems and relayed to GitHub through the repository dispatch Webhook.

svg viewer

Scheduled events are similar to cron jobs. These events trigger workflows on a defined schedule. Scheduled events are a way to automate repetitive tasks, such as performing issue maintenance on older issues in GitHub or running a nightly reporting job.

Manual dispatch events are not triggered through repository activities but rather manually. For example, a project may have a Twitter account associated with it, and project maintainers may want to be able to send a tweet about a new feature but do not want to share the Twitter authentication secrets. An ad hoc event would enable automation to send out the tweet on behalf of the project.

Event syntax#

Events require a developer to specify the type of events for the on: key in the workflow. Event types generally have child key-value pairs that define their behavior.

A single event example #

A single event can be specified to trigger automation:

Example of a single event

A multiple events example#

Multiple events can be specified to trigger automation:

Example of multiple events being triggered

A scheduled event example#

Scheduled event schedules are specified using Portable Operating System Interface (POSIX) cron syntax:

Specifying a scheduled event

A manual event example#

Manual events are triggered through user interaction and can include input fields:

Manual events that require interaction from the user

Context and expressions#

GitHub Actions exposes a rich set of context variables, expressions, functions, and conditionals to provide expressiveness in our workflows. This will not be an exhaustive study of all of these items, but we will highlight the most critical items.

Context variables#

Context variables provide a way to access information about workflow runs, environment, steps, secrets, and so on. The most common context variables are github, env, secrets, and matrix. These variables are treated as maps and can be indexed using variable names and property names. For example, env['foo'] resolves to the value of the foo environment key.

The github context variable provides information about the workflow run and contains information such as the ref that the workflow is executing on. This is useful if we would like to use that information to inject a version into an application at build time. We can access this information by indexing the github variable with github['ref'] or github.ref.

The env context variable contains environment variables specified for the workflow run. The values can be accessed by using the index syntax.

The secrets context variable contains the secrets available for the workflow run. These values can also be accessed by the index syntax. Note that these values will be redacted in the logs, so the secret values will not be exposed.

The matrix context variable contains information about the matrix parameters we configure for the current job. For example, if we want to run a build on multiple operating systems with multiple versions of Go, the matrix variable allows us to specify the list of each one, which can be used to execute a set of concurrent job executions using each combination of operating system and Go version. We will go into more detail about this when we talk about jobs.

Expressions#

The syntax used for an expression is ${{ expression }}. Expressions consist of variables, literals, operators, and functions. Let's examine the following example:

Execution of a correctly labelled job

The preceding job will only execute if the pull request is labeled with safe to test. The if conditional will evaluate the github.event.pull_request.labels.*.name context variable and verify that one of the labels on the pull request is named safe to test. This is useful if we want to ensure that a workflow only executes after a repository maintainer has had an opportunity to verify that the pull request is safe.

Expressions can also be used as input. Let's examine the following example:

Expressions as inputs

The snippet of YAML shows how to set an environment variable called GIT_SHA to the value of the github.sha context variable. The GIT_SHA environment variable will now be available to all actions running within the job. Using context variables for input is useful for customizing the execution of scripts or actions executed in a workflow.

Jobs#

A job is a collection of steps that run on an individual compute instance, or runner. We can think of a runner as a virtual machine for running our job. Jobs, by default, execute concurrently, so if a workflow defines multiple jobs, they will execute concurrently if enough runners are available. Jobs have the concept of dependency where a job can be dependent on another job, which will ensure the jobs execute sequentially rather than concurrently.

Job syntax #

Jobs require a developer to specify an ID of the job, the type of runner the job will execute on using the runs-on: key, and a sequence of steps the job will execute using the steps: key. The runs-on: key is particularly interesting to us, as it is useful for executing a job on different operating system (OS) platforms such as multiple versions of Ubuntu, macOS, and Windows.

With the runs-on: key, a job is able to run on a specified platform, but that does not allow us to make a matrix of jobs to run on multiple platforms concurrently. To enable a job to execute in a matrix of configurations, one must use the strategy: key and expressions. By configuring the strategy, we can build a matrix of jobs executing the same job configuration. We will find an example of this configuration in the following example.

There are many other options to customize the execution of the job and the environment that the job executes within, but we will not dive deeply into them.

Executing jobs on multiple platforms#

This example shows two jobs named job_one and job_two. Here, job_one is a matrix job that will run six concurrent templated jobs on the latest versions of Ubuntu, macOS, and Windows, which will each echo 1.17 and 1.16. Running on Ubuntu 18.04, job_two will run concurrently with job_one and echo "hello world!":

An example of running jobs on different platforms

Steps#

Steps are tasks that run in the context of a job and execute in the context of the job's associated runner. Steps can consist of a shell command or an action. Since steps execute in the same runner, they can share data between each of the steps. For example, if we create a file on the filesystem of the runner in a previous step, subsequent steps will be able to access that file. We can think of a step running within its own process and that any changes to environment variables will not carry over to the next step.

Steps syntax#

Steps require a developer to specify an action with the uses: key or specify the shell commands to run with the run: key. Optional input allows us to customize the environment variables using the env: key and the working directory using the working-directory: key, and also to change the name that appears in the GitHub user interface for the step by using the name key. There are a wide variety of other options to customize the execution of steps, but we will not go into great depth about these.

Step for installing Go using an action#

This example shows a step with no name that uses the v2 version of actions/setup-go to install version 1.17.0 or higher of Go. This action can be found here on GitHub. This is a great example of a publicly available action that we can use to add functionality to our automation. We can find actions for nearly any task on GitHub here.

Steps to install the 1.17.0 version of Go

A step with a multiple line command#

In this example, we've extended the previous one and added a Run go mod download and test step that runs the go tool, which was installed by actions/setup-go@v2. The run command uses | in the first line to indicate the start of a multiline string in YAML:

Running multiple commands in steps

Actions#

An action is a reusable combination of a set of steps formed into a single command, which can also have input and output. For example, the actions/setup-go action is used to execute a series of steps to install a version of Go on a runner. The Go toolchain can then be used within subsequent steps within the same job.

GitHub Actions is aptly named, as actions are the superpower of GitHub Actions. Actions are often published publicly and enable developers to leverage existing recipes to build complex automation quickly. Actions are similar to open-source Go libraries, which enable developers to build Go applications quicker. As we build our own actions, we will quickly see the power of this feature.

Introduction

Build and Trigger Our First GitHub Action